package sd;


import java.net.URI;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Hashtable;
import java.util.ListIterator;
import java.util.Map;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import snbenchlib.XML;
import step.ExpNode;
import step.Graph;
import step.Node;
import step.parse.SyntaxError;
import sxe.sensors.GenericSensor;

public class ManagedSXE {
	private int 		availResources; /* [0..100] */
	private URI 		uri; 			/* e.g., http://machine/snbench/sxe/ */
	private String 		id;				
	private Calendar	timeLastSeen;	/* indicates when the node had last phoned home */
	private ArrayList<GenericSensor> sensors;		/* The sensors connected to this SXE */
	
	private Hashtable<String, Integer> htNeighbors;		/* the SXEs (IDs) connected to this 
														   SXE and their "distance == RTT" */
	
	public ManagedSXE(String _id, URI _uri, int _resources, ArrayList<GenericSensor> _sensors){
		id = _id;
		uri = _uri;
		timeLastSeen = Calendar.getInstance();
		availResources = _resources;
		sensors = _sensors;
		htNeighbors = new Hashtable<String, Integer>();
	}

	public ArrayList<GenericSensor> getSensors(){
		return sensors;
	}
	
	public String toString() {
		return "<sxe xmlns=\"" + XML.XMLNS + "\" " +
				"id=\"" + id + "\" " +
				"resources=\"" + availResources +  "\" " +
				"href=\"" + uri.toString() + "\"/>\n";
	}

	
	public String getXML() {
		return getXML("");
	}
	
	public String getXML(String strURIPath) {
		
	    SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy\th:mm:ss a");
		String sTime = formatter.format(timeLastSeen.getTime());

		String s = "<sxe xmlns=\"" + XML.XMLNS + "\" " +
				"id=\""	+ id + "\" " + 
				"resources=\"" + availResources + "\" " +
				"lastreport=\"" + sTime + "\" " +
				"href=\"" + uri.toString() + strURIPath + "\">\n";
		
		s += "<sensorDevices>";
		for(GenericSensor sensor:sensors){
			s += sensor.GetMinimalXML(); 
		}
		s += "</sensorDevices>";

		// serialize the neighborhood info of this node
		if(htNeighbors.size()>0){
			s+="<connectivity>";
			for(Map.Entry<String, Integer> e : htNeighbors.entrySet()){
				s+="<neighbor id=\"" + e.getKey() + "\" distance=\"" + e.getValue() + "\"/>";
			}
			s+="</connectivity>";
		}else
			s+="<connectivity/>";

		s += "</sxe>";
		return s;
	}

	/**
	 * Sets "timeLastSeen" to be right now.
	 * availResources updated to availResources from message
	 * updates uri to URI
	 * 
	 * returns the difference between this nodes's resource score
	 * and the reported score (i.e., how much we need to change
	 * the overall resource view based on this report).
	 */
	public int update(ManagedSXE updatedMessage) {
		timeLastSeen = Calendar.getInstance();
		uri = updatedMessage.getUri();
		System.err.println("Previous message from " + updatedMessage.id + " had resources " + availResources + ", new message reports " + updatedMessage.availResources );
		if(availResources != updatedMessage.availResources){
			int diff = availResources - updatedMessage.availResources;
			availResources = updatedMessage.availResources;
			return diff;
		}
		return 0;
	}
	
	/**
	 * @return true if "timeLastSeen" is older than 10 minutes ago.
	 */
	public boolean checkExpired(){
		Calendar cTmp = Calendar.getInstance();
		cTmp.add(Calendar.MINUTE,-2);
		return(timeLastSeen.before(cTmp));
	}

	public int getAvailResources() {
		return availResources;
	}

	public void setAvailResources(int availResources) {
		this.availResources = availResources;
	}

	public void incrAvailResources(int amount){
		this.availResources+=amount;
	}
	
	public void decrAvailResources(int amount){
		this.availResources-=amount;
	}
	public URI getUri() {
		return uri;
	}

	public void setUri(URI uri) {
		this.uri = uri;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	/** 
	 * Generates a managed SXE object from given XML string
	 * 
	 * @param string
	 * @return
	 */
	public static ManagedSXE Parse(String string) throws SyntaxError{
		ManagedSXE mSXE = null;
		GenericSensor gs;
		ArrayList<GenericSensor> sensors = null;

		Element eSXE = XML.parseXMLAndReturnRootElement(string,"sxe");
		String href = eSXE.getAttribute("href");
		if(href==null)
			throw new SyntaxError("no href attribute in received XML");
		String _id = eSXE.getAttribute("id");
		if(_id==null)
			throw new SyntaxError("no id attribute in received XML");

		URI _uri;
		try {
			_uri = new URI(href);
		} catch (URISyntaxException e) {
			e.printStackTrace();
			throw new SyntaxError("failed to create URI from href attribute.");
		}
	
		/* parse resources */
		int _resources;
		String res = eSXE.getAttribute("resources");
		try{
			_resources = Integer.valueOf(res);
		}catch(NumberFormatException nfe){
			// TODO: a nicer job when the SXE has no resource score.
			_resources = 100;
		}

		/* Parse the sensor device list */	
		NodeList nl = eSXE.getElementsByTagName("sensorDevices");
		if(nl.getLength()>1)
			throw new SyntaxError("multiple sensorDevices nodes!?");
		
		sensors = new ArrayList<GenericSensor>();
		
		if(nl.getLength()>0){
			NodeList nlSensors = nl.item(0).getChildNodes();
			for(int j=0;j<nlSensors.getLength();j++){
				if(nlSensors.item(j).getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
					gs = GenericSensor.xmlParse((Element) nlSensors.item(j));
					if(gs!=null){
						sensors.add(gs);
					}
				}
			}
			
		}

		/* parse connectivity information */
		nl = eSXE.getElementsByTagName("connectivity");
		Hashtable<String,Integer> htConnectivity = new Hashtable<String,Integer>();
		
		if(nl.getLength() > 0){
			Element e = (Element)nl.item(0);
			nl = e.getElementsByTagName("neighbor");
			for(int i=0;i<nl.getLength();i++){
				Element n = (Element)nl.item(i);
				String id = n.getAttribute("id");
				String dist = n.getAttribute("distance");
				if((dist!=null)&&(id!=null))
					htConnectivity.put(id,new Integer(Integer.parseInt(dist)));
			}
		}

		/* TODO: Theoretically, a SD could be passed an SXE that already has
		 * 		STEP nodes attached to it (e.g., an SD handoff of SXE resources
		 * 		when ,say, an SXE goes down).
		 */
		
		mSXE = new ManagedSXE(_id, _uri, _resources, sensors);
		mSXE.setConnectivity(htConnectivity);
		
		return mSXE;
	}

	
	private void setConnectivity(Hashtable<String, Integer> htConnectivity) {
		htNeighbors = htConnectivity;
	}

	/**
	 * Should always be called after try re-use.  Literally this
	 * method assumes reuse can not happen so ensures that these
	 * computations are:
	 * (1) compatible with the resources on this machine
	 * (2) able to be deployed to this machine
	 * @param list
	 * @return true/false
	 */
	public boolean canAccomidate(ArrayList<String> list, Graph g) {
		ListIterator<String> li = list.listIterator();
		while(li.hasNext()){
			String s = li.next();
			Node n = g.GetNode(s);
			if(n instanceof ExpNode){
				/* TODO: need some notion of which opcodes
				 * require sensors and which don't ... */
				String sSensorReq = ((ExpNode)n).getOpcode();
			}
		}
		
		return true;
	}

}